﻿using System;
using System.IO;

namespace ArgusPCLib
{
	/// <include file='Documentation/CRC32.xml' path='CRC32/*'/>
	public class CRC32
	{
		uint[] table;

		private uint currentCRC;

		/// <summary>
		/// Gets the current CRC32 value.
		/// </summary>
		/// <seealso cref="Add"/>
		/// <seealso cref="Clear"/>
		public int CurrentCRC
		{
			get
			{
				uint x = 0xffffffff;
				return (int)(this.currentCRC ^ x);
			}
		}

		private CRC32()
		{
			this.table = new uint[256];
			this.Clear();
		}

		/// <summary>
		/// Creates the data-table used to calculate CRC32 values and returns an instance of <see cref="CRC32"/>
		/// </summary>
		/// <returns>A new instance of <see cref="CRC32"/>.</returns>
		public static CRC32 CreateTable()
		{
			Delegate createTable = new Func<CRC32>(CRC32.CreateTable);
			Bird.Logger.LogMethodEnter(Bird.LogVerbosities.All, createTable, null);

			CRC32 RetVal = new CRC32();
			for (int n = 0; n < RetVal.table.Length; n++)
			{
				uint c = (uint)n;
				for (int k = 0; k < 8; k++)
				{
					if ((c & 1) == 1)
						c = 0xedb88320 ^ (c >> 1);
					else
						c = c >> 1;
				}
				RetVal.table[n] = c;
			}

			Bird.Logger.LogMethodLeave(Bird.LogVerbosities.All, createTable, new object[] { RetVal });
			return RetVal;
		}

		private uint Update(uint crc, byte[] data)
		{
			return this.Update(crc, data, 0, data.Length);
		}

		private uint Update(uint crc, byte b)
		{
			return this.table[(crc ^ (uint)b) & 0xFF] ^ (crc >> 8);
		}

		private uint Update(uint crc, byte[] data, int offset, int count)
		{
			for (int n = offset; n < count; n++)
			{
				crc = this.Update(crc, data[n]);
			}
			return crc;
		}

		private uint Update(uint crc, Stream stream, int count)
		{
			for (int n = 0; n < count; n++)
			{
				int b = stream.ReadByte();
				if (b < 0)
					throw new EndOfStreamException();
				crc = this.Update(crc, (byte)b);
			}
			return crc;
		}

		/// <summary>
		/// Updates the value in <see cref="CurrentCRC"/> with the data from a <see cref="Stream"/>.
		/// </summary>
		/// <param name="stream">The stream from which the data is read.</param>
		/// <param name="count">The number of bytes to read from <paramref name="stream"/>.</param>
		public void Add(Stream stream, int count)
		{
			this.currentCRC = this.Update(this.currentCRC, stream, count);
		}

		/// <summary>
		/// Updates the value in <see cref="CurrentCRC"/> with the data in a <see cref="byte"/>.
		/// </summary>
		/// <param name="b">The data for which the CRC32 value is updated.</param>
		public void Add(byte b)
		{
			this.currentCRC = this.Update(this.currentCRC, b);
		}

		/// <summary>
		/// Updates the value in <see cref="CurrentCRC"/> with the data in a <see cref="byte[]"/>.
		/// </summary>
		/// <param name="data">The data for which the CRC32 value is updated.</param>
		public void Add(byte[] data)
		{
			this.currentCRC = this.Update(this.currentCRC, data);
		}

		/// <summary>
		/// Updates the value in <see cref="CurrentCRC"/> with the data in a <see cref="byte[]"/>.
		/// </summary>
		/// <param name="data">The data for which the CRC32 value is updated.</param>
		/// <param name="offset">The zero-based index for where to start reading bytes in <paramref name="data"/>.</param>
		/// <param name="count">The number of bytes to read from <paramref name="data"/>.</param>
		public void Add(byte[] data, int offset, int count)
		{
			this.currentCRC = this.Update(this.currentCRC, data, offset, count);
		}

		/// <summary>
		/// Resets <see cref="CurrentCRC"/> to 0.
		/// </summary>
		public void Clear()
		{
			this.currentCRC = 0xffffffff;
		}

		/// <summary>
		/// Calculates and returns the CRC32 value for an array of bytes independent of any pervious calculations.
		/// </summary>
		/// <param name="data">The data for which the CRC32 should be calculated.</param>
		/// <returns>The calculated CRC32 value.</returns>
		public int Calculate(byte[] data)
		{
			return this.Calculate(data, 0, data.Length);
		}

		/// <summary>
		/// Calculates and returns the CRC32 value for an array of bytes independent of any pervious calculations.
		/// </summary>
		/// <param name="data">The data for which the CRC32 should be calculated.</param>
		/// <param name="offset">The zero-based index for where to start reading bytes in <paramref name="data"/>.</param>
		/// <param name="count">The number of bytes to read from <paramref name="data"/>.</param>
		/// <returns>The calculated CRC32 value.</returns>
		public int Calculate(byte[] data, int offset, int count)
		{
			uint crc = 0xffffffff;
			return (int)(this.Update(crc, data, offset, count) ^ crc);
		}

		/// <summary>
		/// Calculates and returns the CRC32 value for binary data read from a <see cref="Stream"/>.
		/// </summary>
		/// <param name="stream">The stream from which the data is read.</param>
		/// <param name="count">The number of bytes to read from <paramref name="stream"/>.</param>
		/// <returns>The calculated CRC32 value.</returns>
		public int Calculate(Stream stream, int count)
		{
			uint crc = 0xffffffff;
			return (int)(this.Update(crc, stream, count) ^ crc);
		}
	}
}
